/**
 * 
 */
package ro.dta.idbi.patterns;

import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import ro.dta.idbi.interfaces.IExtentGenerator;
import ro.dta.idbi.interfaces.IntegrationOperation;
import ro.dta.idbi.model.AbstractPattern;
import ro.dta.idbi.model.AutoMedUtils;
import ro.dta.idbi.model.Utils;
import uk.ac.ic.doc.automed.IntegrityException;
import uk.ac.ic.doc.automed.NotFoundException;
import uk.ac.ic.doc.automed.reps.Schema;
import uk.ac.ic.doc.automed.reps.SchemaObject;

/**
 * <b>Columns to supertype transformation pattern</b><br />
 * 
 * Example of use:<br />
 * 
 * <code>cols_to_supertype(&lt;&lt;parent_table&gt;&gt;, &lt;&lt;table1,col&gt;&gt;,&lt;&lt;table2,col&gt;&gt;, ..., &lt;&lt;tablen,col&gt;&gt;)</code>
 * <br />
 * 
 * Will move the columns named <i>col</i> from the child tables to the supertype
 * table <i>parent_table</i>
 * 
 * @author Tudor Dobrila
 * 
 */
public class ColToSupertype extends AbstractPattern {

	/**
	 * Name of the pattern
	 */
	public static final String NAME = "Columns to supertype";

	/**
	 * Description of the pattern
	 */
	public static final String DESCRIPTION = "Move columns to the supertype table";

	/**
	 * Name of the command associated with the pattern
	 */
	public static final String COMMAND = "cols_to_supertype";

	/**
	 * Integration phase during which the command is applied
	 */
	public static final EnumSet<IntegrationOperation> OPERATION = EnumSet
			.of(IntegrationOperation.CONFORMING, IntegrationOperation.IMPROVEMENT);

	/**
	 * Name of the supertype table
	 */
	private transient String superTbl;

	/**
	 * Columns names
	 */
	private transient List<String> colsStr;

	/**
	 * (Table,column) pairs of the columns in the child tables
	 */
	private transient List<Map.Entry<String, String>> cols;

	/**
	 * Definition of the column being moved to the supertype
	 */
	private transient Object[] colDef;

	/**
	 * Columns to supertype pattern class constructor
	 * 
	 * @param sourceSchema
	 *            Source schema over which the transformation is applied
	 */
	public ColToSupertype(final Schema sourceSchema) throws NotFoundException, IntegrityException {
		super(sourceSchema);

		pattern = Pattern.compile("^" + COMMAND + "[ ]*\\((" + TABLE_REGEX + ")" + COMMA_REGEX
				+ COL_SEQ_REGEX + "\\)$");
	}

	/**
	 * Columns to supertype pattern class constructor
	 * 
	 * @param sourceSchema
	 *            Name of the source schema over which the transformation is
	 *            applied
	 */
	public ColToSupertype(final String sourceSchema) throws NotFoundException, IntegrityException {
		this(Schema.getSchema(sourceSchema));
	}

	/**
	 * Columns to supertype pattern class constructor
	 * 
	 * @param sourceSchema
	 *            Name of the source schema over which the transformation is
	 *            applied
	 * @param superTbl
	 *            Name of the parent table
	 * @param colsStr
	 *            List of strings, representing the columns to be moved to the
	 *            parent
	 * @param cols
	 *            List of (table,column) pairs, representing the columns to be
	 *            moved to the parent
	 * @param colDef
	 *            Definition of the column being moved to the supertype
	 */
	public ColToSupertype(final Schema sourceSchema, final String superTbl,
			final List<String> colsStr, final List<Map.Entry<String, String>> cols, Object[] colDef)
			throws NotFoundException, IntegrityException {
		this(sourceSchema);

		this.superTbl = superTbl;
		this.colsStr = colsStr;
		this.cols = cols;
		this.colDef = colDef;
	}

	@Override
	public List<String> getPrimitiveCommands() throws NotFoundException {
		List<String> result = new ArrayList<String>();

		final SchemaObject superTblObj = sourceSchema.getSchemaObject("<<" + superTbl + ">>");
		final IExtentGenerator gen = new ExtentGenerator(superTbl, colsStr, cols);

		// Step 1: Create the new column in the supertype
		String iql = gen.getExtentsForStep(1).get(0);
		StringBuilder cmd = new StringBuilder();
		cmd.append("add (").append(
				Utils.genColRepresentation(superTblObj.getValue(), colDef[1], "null", colDef[3]));
		cmd.append(",").append(iql).append(")");
		result.add(cmd.toString());

		// Step 2: Delete the columns from the child tables
		List<String> extents = gen.getExtentsForStep(2);
		for (int i = 0; i < cols.size(); i++) {
			cmd.setLength(0);
			cmd.append("delete (");
			cmd.append(Utils.genColRepresentation(cols.get(i).getKey(), colDef[1], "notnull",
					colDef[3]));
			cmd.append(",").append(extents.get(i)).append(")");
			result.add(cmd.toString());
		}
		
		return result;
	}

	/**
	 * Check if parentTbl is a supertype of all other tables and, in addition,
	 * all columns have the same name
	 */
	@Override
	protected boolean verify() {
		// Check if superTbl is a supertype of all tables and all columns have
		// the same name
		final String colName = cols.get(0).getValue();
		for (Map.Entry<String, String> col : cols) {
			SchemaObject crtTblObj;
			try {
				crtTblObj = sourceSchema.getSchemaObject("<<" + col.getKey() + ">>");
				if (!col.getValue().equals(colName)) {
					return false;
				}
				final List<String> supertypes = AutoMedUtils.getSupertypes(sourceSchema, crtTblObj);
				if (!supertypes.contains(superTbl)) {
					return false;
				}
			} catch (NotFoundException e) {
				return false;
			}
		}

		return true;
	}

	@Override
	protected void parse(final String trans) {
		final Matcher matcher = pattern.matcher(trans);
		matcher.find();

		superTbl = parseTableName(matcher.group(1));
		final List<String> columns = splitValues(matcher.group(2));
		colsStr = new ArrayList<String>();
		cols = new ArrayList<Map.Entry<String, String>>();

		for (int i = 0; i < columns.size(); i += 2) {
			final String crtCol = columns.get(i) + "," + columns.get(i + 1);
			colsStr.add(crtCol);
			cols.add(parseColName(crtCol));
		}

		SchemaObject colObj;
		try {
			colObj = sourceSchema.getSchemaObject(colsStr.get(0));
		} catch (NotFoundException e) {
			throw new IllegalArgumentException("Schema object " + colsStr.get(0)
					+ " does not exist.");
		}
		colDef = colObj.getSchemeDefinition();
	}

	/**
	 * @return The parent table
	 */
	public String getSuperTbl() {
		return superTbl;
	}

	/**
	 * @return the The columns in the child tables as a list of string
	 */
	public List<String> getColsStr() {
		return colsStr;
	}

	/**
	 * Extent generator for the <i>Columns to supertype transformation
	 * pattern</i>
	 * 
	 * @author Tudor Dobrila
	 * 
	 */
	static class ExtentGenerator implements IExtentGenerator {
		private final String superTbl;
		private final List<String> colsStr;
		private final List<Entry<String, String>> cols;

		public ExtentGenerator(final String superTbl, final List<String> colsStr,
				final List<Map.Entry<String, String>> cols) {
			this.superTbl = superTbl;
			this.colsStr = colsStr;
			this.cols = cols;
		}

		@Override
		public List<String> getExtentsForStep(final int step) {
			List<String> result = new ArrayList<String>();
			StringBuilder iql = new StringBuilder();
			switch (step) {
			case 1:
				// Step 1: Create the new attribute in the supertype
				for (String c : colsStr) {
					iql.append(c).append(" ++ ");
				}
				// iql.append("[ {x,Null} | {x} <- (<<").append(superTbl).append(">>");
				// for (Map.Entry<String, String> c : cols) {
				// iql.append(" -- <<").append(c.getKey()).append(">>");
				// }
				// iql.append(") ]");
				iql.setLength(iql.length() - 4);
				result.add(iql.toString());
				break;
			case 2:
				// Step 2: Delete the columns from the child tables
				for (int i = 0; i < cols.size(); i++) {
					final Map.Entry<String, String> col = cols.get(i);
					iql.setLength(0);
					iql.append("[ {x,").append(col.getValue()).append("} | ");
					iql.append("{x} <- <<").append(col.getKey()).append(">>; ");
					iql.append("{x,").append(col.getValue()).append("} <- <<").append(superTbl)
							.append(",");
					iql.append(col.getValue()).append(">> ]");

					result.add(iql.toString());
				}
				break;
			default:
			}

			return result;
		}

	}

}
